#!/usr/bin/env bash

set -Eeuo pipefail
# no shopt -s inherit_errexit as this needs to run on the ancient Bash in RHEL7
# instead, has 'set -e; ' below as recommended by shellcheck

# pass the packages to test (without tests directory) as args to the script

# shellcheck source-path=SCRIPTDIR
. cki_utils.sh

function did_coverage_decrease() {
    local pipelines_json pipeline_id pipeline_json old_coverage new_coverage=${1}
    shift
    echo -n "  Getting last successful pipeline... "
    if ! [[ -v CI_MERGE_REQUEST_PROJECT_ID ]]; then
        cki_echo_yellow "skipped because not in CI MR job"
        return 1
    fi
    if [[ -v CI_MERGE_REQUEST_TARGET_BRANCH_SHA ]]; then
        # for merged result pipelines, the change of the MR is introduced by
        # the merge commit, so take the coverage of the last target branch
        # commit right before the merge commit as the reference
        # shellcheck disable=SC2154
        pipelines_json=$(curl -Ss "${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/pipelines?sha=${CI_MERGE_REQUEST_TARGET_BRANCH_SHA}&status=success")
    else
        # shellcheck disable=SC2154
        pipelines_json=$(curl -Ss "${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/pipelines?ref=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}&status=success")
    fi
    pipelines_length=$(jq '. | length' <<< "${pipelines_json}")
    if (( pipelines_length == 0 )); then
        cki_echo_yellow "skipped because no successful pipeline found"
        return 1
    fi
    pipeline_id=$(jq '.[0].id' <<< "${pipelines_json}")
    cki_echo_green " ${pipeline_id}"
    echo -n "  Getting old coverage... "
    pipeline_json=$(curl -Ss "${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/pipelines/${pipeline_id}")
    old_coverage=$(jq -r '.coverage // "0.00"' <<< "${pipeline_json}")
    # round up with a precision of two decimals, eg 65.9518 -> 65.96
    if python -c "import math; exit(math.ceil(${new_coverage} * 100) / 100 < ${old_coverage})"; then
        cki_echo_green "${old_coverage}%"
        return 1
    else
        if [[ -v CI_MERGE_REQUEST_TARGET_BRANCH_SHA ]]; then
            # for merged result pipelines, also check whether lines of code
            # increased, and only fail the coverage check in that case
            if git diff --numstat "${CI_MERGE_REQUEST_TARGET_BRANCH_SHA}..HEAD" -- "$@" | \
                awk '{ added += $1; removed += $2 } END { exit added - removed > 0 }'; then
                cki_echo_yellow "${old_coverage}% higher than new ${new_coverage}%, but line count did not increase"
                return 1
            else
                cki_echo_red "${old_coverage}% higher than new ${new_coverage}%, and line count increased"
                return 0
            fi
        else
            cki_echo_red "${old_coverage}% higher than new ${new_coverage}%"
            return 0
        fi
    fi
}


function run_pytest() {
    if [[ -f tests/__init__.py ]]; then
        cki_echo_yellow "Running tests under coverage with: pytest" "${pytest_args[@]}"
        if ! coverage run --source "$(IFS=,; echo "$*")" --branch --parallel-mode -m pytest "${pytest_args[@]}"; then
            FAILED+=(pytest)
        fi
        coverage combine --quiet

        cki_echo_yellow "Generating coverage reports"
        # shellcheck disable=SC2154,SC2310
        if ! cki_is_true "${CKI_COVERAGE_ENABLED:-true}"; then
            cki_echo_yellow "  skipped because CKI_COVERAGE_ENABLED is false"
            return 0
        fi
        coverage report -m || true
        coverage html -d coverage/ || true
        coverage xml -o coverage/coverage.xml || true

        cki_echo_yellow "Total coverage and trend"
        # shellcheck disable=SC2154,SC2310
        if ! cki_is_true "${CKI_COVERAGE_CHECK_ENABLED:-true}"; then
            cki_echo_yellow "  skipped because CKI_COVERAGE_CHECK_ENABLED is false"
            return 0
        fi
        new_coverage=$(coverage json -o - | jq .totals.percent_covered)
        echo "  COVERAGE: ${new_coverage}%"

        did_coverage_decrease "${new_coverage}" "$@" &
        wait $! && coverage_decreased=1 || coverage_decreased=0
        if [[ ${coverage_decreased} = 1 ]]; then
            # Match "[skip coverage check]" but also "\[skip coverage check\]"
            # as it's easy to accidentally get backslashes into
            # your MR description if you use the raw-text editor
            # shellcheck disable=SC2154
            if [[ ! "${CI_MERGE_REQUEST_DESCRIPTION}" =~ ((\\?)\[skip coverage check(\\?)\]) || "${CI_MERGE_REQUEST_LABELS:-}" =~ "skip_coverage_check" ]]; then
                FAILED+=(coverage)
                cki_echo_red "  Include [skip coverage check] in the merge request description or skip_coverage_check label to skip this check"
                if [[ "${CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED:-false}" == 'true' ]]; then
                    cki_echo_red "  Note your MR length exceeds CI variable length limits; place commands at the TOP of the description or use a label to avoid failures".
                fi
            fi
        fi
    fi
}

cki_say "preparing"
# shellcheck disable=SC2154,SC2310
if cki_is_true "${CKI_SKIP_PREPARE:-false}"; then
    cki_echo_yellow "skipping prepare because CKI_SKIP_PREPARE is true"
else
    PIP_INSTALL=(python3 -m pip install)
    python_type=$(type -P python3)
    if [[ ${python_type} = /usr* ]]; then
        PIP_INSTALL+=(--user)
    fi
    if [[ -f constraints.txt ]]; then
        PIP_INSTALL+=(--constraint constraints.txt)
    fi

    # in MR jobs, work around stale cki-lib from the gitlab-runner cache
    if [[ -v CI_MERGE_REQUEST_PROJECT_ID ]] && [[ -z "${cki_lib_pip_url:-}" ]] && \
            grep -F 'git+https://gitlab.com/cki-project/cki-lib.git' setup.cfg; then
        cki_lib_pip_url=git+https://gitlab.com/cki-project/cki-lib.git@production
    fi

    # if cki-lib requires overriding, update it and restart the script.
    if [[ -n "${cki_lib_pip_url:-}" ]] && ! [[ -v SKIP_FURTHER_CKI_TEST_EXEC ]]; then
        cki_echo_yellow "Using cki-lib override: ${cki_lib_pip_url}"
        python3 -m pip uninstall -y "cki-lib"
        echo "Running ${PIP_INSTALL[*]} ${cki_lib_pip_url}"
        "${PIP_INSTALL[@]}" "${cki_lib_pip_url}"
        cki_echo_yellow "Re-executing cki_test.sh from updated cki-lib"
        SKIP_FURTHER_CKI_TEST_EXEC=true exec cki_test.sh "$@"
    fi

    # Install testing requirements (append "@$cki_lib_pip_url" if defined)
    TEST_REQS="cki-lib[test]${cki_lib_pip_url:+@${cki_lib_pip_url}}"
    cki_echo_yellow "Installing testing requirements with: ${PIP_INSTALL[*]} ${TEST_REQS}"
    "${PIP_INSTALL[@]}" "${TEST_REQS}"

    overrides_str=$(compgen -A variable | grep '_pip_url$' || true)
    readarray -t overrides <<< "${overrides_str}"
    for override in "${overrides[@]}"; do
        if [[ -z ${override} ]] || [[ ${!override} != git+https://* ]]; then
            continue
        fi
        pip_url=${!override}
        package_name=$(set -e; cki_git_clean_url "${pip_url%@*}")
        package_name=${package_name%.git/}
        package_name=${package_name##*/}

        # cki-lib was already handled
        if [[ ${package_name} = cki-lib ]]; then
            continue
        fi

        cki_echo_yellow "Found ${package_name} override: ${pip_url}"
        python3 -m pip uninstall -y "${package_name}"
        "${PIP_INSTALL[@]}" "${pip_url}"
    done
fi # end of prepare

read -ra pytest_args <<< "${CKI_PYTEST_ARGS:---verbose -r s}"
read -ra pytest_ignorelist <<< "${CKI_PYTEST_IGNORELIST:-inttests/}"
pytest_args+=(
    --junitxml=coverage/junit.xml
    "${pytest_ignorelist[@]/#/--ignore=}"
    --color=yes
)

cki_say "testing"

FAILED=()

run_pytest "$@"

if (( ${#FAILED[@]} > 0 )); then
    cki_echo_red "Failed testing steps: ${FAILED[*]}"
    exit 1
fi
